/*
 * Created on 22 Aug 2008
 * Created by Allan Crooks
 * Copyright (C) 2008 Vuze Inc., All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package org.gudy.azureus2.ui.console.commands;

import java.io.File;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;

import org.gudy.azureus2.core3.util.AESemaphore;
import org.gudy.azureus2.core3.util.AEThread2;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.FileUtil;
import org.gudy.azureus2.plugins.PluginException;
import org.gudy.azureus2.plugins.PluginInterface;
import org.gudy.azureus2.plugins.PluginManager;
import org.gudy.azureus2.plugins.installer.InstallablePlugin;
import org.gudy.azureus2.plugins.installer.PluginInstallationListener;
import org.gudy.azureus2.plugins.installer.PluginInstaller;
import org.gudy.azureus2.plugins.installer.StandardPlugin;
import org.gudy.azureus2.plugins.update.Update;
import org.gudy.azureus2.plugins.update.UpdateCheckInstance;
import org.gudy.azureus2.plugins.update.UpdateCheckInstanceListener;
import org.gudy.azureus2.plugins.update.UpdateManager;
import org.gudy.azureus2.plugins.utils.resourcedownloader.ResourceDownloader;
import org.gudy.azureus2.plugins.utils.resourcedownloader.ResourceDownloaderAdapter;
import org.gudy.azureus2.pluginsimpl.update.sf.SFPluginDetails;
import org.gudy.azureus2.pluginsimpl.update.sf.SFPluginDetailsLoaderFactory;
import org.gudy.azureus2.ui.console.ConsoleInput;
import org.gudy.azureus2.ui.console.util.TextWrap;

public class Plugin extends IConsoleCommand {

    public Plugin() {
        super("plugin");
    }

    public String getCommandDescriptions() {
        return ("plugin [various options]\t\tRun with no parameter for more help.");
    }

    public void printHelpExtra(PrintStream out, List args) {
        out.println("> -----");
        out.println("Subcommands:");
        out.println("install\t[pluginid]\tLists plugins available to install or installs a given plugin");
        out.println("location\t\tLists where plugins are being loaded from");
        out.println("list\t\t\tList all running plugins");
        out.println("listall\t\t\tList all plugins - running or not");
        out.println("status pluginid\t\tPrints the status of a given plugin");
        out.println("startup pluginid on|off\tEnables or disables the plugin running at startup");
        out.println("uninstall pluginid\t\tUninstalls a plugin");
        out.println("update\t\tUpdates all plugins with outstanding updates");
        out.println("> -----");
    }

    public void execute(String commandName, final ConsoleInput ci, List args) {
        if (args.isEmpty()) {
            printHelpExtra(ci.out, args);
            return;
        }

        String subcmd = (String) args.get(0);
        if (!java.util.Arrays.asList(new String[] { "location", "list", "listall", "status", "startup", "install", "uninstall", "update" })
                .contains(subcmd)) {
            ci.out.println("Invalid subcommand: " + subcmd);
            ci.out.println();
            return;
        }

        PluginManager plugin_manager = ci.getCore().getPluginManager();

        if (subcmd.equals("list") || subcmd.equals("listall")) {
            boolean all_plugins = subcmd.equals("listall");
            ci.out.println("> -----");
            PluginInterface[] plugins = plugin_manager.getPluginInterfaces();
            TreeSet plugin_ids = new TreeSet(String.CASE_INSENSITIVE_ORDER);
            for (int i = 0; i < plugins.length; i++) {
                if (!all_plugins && !plugins[i].getPluginState().isOperational()) {
                    continue;
                }
                String plugin_id = plugins[i].getPluginID();
                plugin_ids.add(plugin_id);
            }
            TextWrap.printList(plugin_ids.iterator(), ci.out, "   ");
            ci.out.println("> -----");
            return;
        }

        if (subcmd.equals("location")) {
            // Taken from ConfigSectionPlugins.
            File fUserPluginDir = FileUtil.getUserFile("plugins");
            String sep = File.separator;

            String sUserPluginDir;

            try {
                sUserPluginDir = fUserPluginDir.getCanonicalPath();
            } catch (Throwable e) {
                sUserPluginDir = fUserPluginDir.toString();
            }

            if (!sUserPluginDir.endsWith(sep)) {
                sUserPluginDir += sep;
            }

            File fAppPluginDir = FileUtil.getApplicationFile("plugins");

            String sAppPluginDir;

            try {
                sAppPluginDir = fAppPluginDir.getCanonicalPath();
            } catch (Throwable e) {
                sAppPluginDir = fAppPluginDir.toString();
            }

            if (!sAppPluginDir.endsWith(sep)) {
                sAppPluginDir += sep;
            }

            ci.out.println("Shared plugin location:");
            ci.out.println("  " + sAppPluginDir);
            ci.out.println("User plugin location:");
            ci.out.println("  " + sUserPluginDir);
            ci.out.println();
            return;
        }

        if (subcmd.equals("update")) {

            if (args.size() != 1) {

                ci.out.println("Usage: update");

                return;
            }

            UpdateManager update_manager = plugin_manager.getDefaultPluginInterface().getUpdateManager();

            final UpdateCheckInstance checker = update_manager.createUpdateCheckInstance();

            checker.addListener(new UpdateCheckInstanceListener() {
                public void cancelled(UpdateCheckInstance instance) {

                }

                public void complete(UpdateCheckInstance instance) {
                    Update[] updates = instance.getUpdates();

                    try {

                        for (Update update : updates) {

                            ci.out.println("Updating " + update.getName());

                            for (ResourceDownloader rd : update.getDownloaders()) {

                                rd.addListener(new ResourceDownloaderAdapter() {
                                    public void reportActivity(ResourceDownloader downloader, String activity) {
                                        ci.out.println("\t" + activity);
                                    }

                                    public void reportPercentComplete(ResourceDownloader downloader, int percentage) {
                                        ci.out.println("\t" + percentage + "%");

                                    }
                                });

                                rd.download();
                            }
                        }

                        boolean restart_required = false;

                        for (int i = 0; i < updates.length; i++) {

                            if (updates[i].getRestartRequired() == Update.RESTART_REQUIRED_YES) {

                                restart_required = true;
                            }
                        }

                        if (restart_required) {

                            ci.out.println("**** Restart required to complete update ****");
                        }
                    } catch (Throwable e) {

                        ci.out.println("Plugin update failed: " + Debug.getNestedExceptionMessage(e));
                    }
                }
            });

            checker.start();

            return;
        }

        if (subcmd.equals("install")) {

            if (args.size() == 1) {

                ci.out.println("Contacting plugin repository for list of available plugins...");

                try {
                    SFPluginDetails[] plugins = SFPluginDetailsLoaderFactory.getSingleton().getPluginDetails();

                    for (SFPluginDetails p : plugins) {

                        String category = p.getCategory();

                        if (category != null) {

                            if (category.equalsIgnoreCase("hidden") || (category.equalsIgnoreCase("core"))) {

                                continue;
                            }
                        }

                        String id = p.getId();

                        if (plugin_manager.getPluginInterfaceByID(id, false) == null) {

                            String desc = p.getDescription();

                            int pos = desc.indexOf("<br");

                            if (pos > 0) {

                                desc = desc.substring(0, pos);
                            }

                            ci.out.println("\t" + id + ": \t\t" + desc);
                        }
                    }

                } catch (Throwable e) {

                    ci.out.println("Failed to list plugins: " + Debug.getNestedExceptionMessage(e));
                }
            } else {

                String target_id = (String) args.get(1);

                if (plugin_manager.getPluginInterfaceByID(target_id, false) != null) {

                    ci.out.println("Plugin '" + target_id + "' already installed");

                    return;
                }

                final PluginInstaller installer = plugin_manager.getPluginInstaller();

                try {
                    final StandardPlugin sp = installer.getStandardPlugin(target_id);

                    if (sp == null) {

                        ci.out.println("Plugin '" + target_id + "' is unknown");

                        return;
                    }

                    new AEThread2("Plugin Installer") {
                        public void run() {
                            try {
                                Map<Integer, Object> properties = new HashMap<Integer, Object>();

                                properties.put(UpdateCheckInstance.PT_UI_STYLE, UpdateCheckInstance.PT_UI_STYLE_NONE);

                                properties.put(UpdateCheckInstance.PT_UI_DISABLE_ON_SUCCESS_SLIDEY, true);

                                final AESemaphore sem = new AESemaphore("plugin-install");

                                final boolean[] restart_required = { false };

                                UpdateCheckInstance instance =
                                        installer.install(new InstallablePlugin[] { sp }, false, properties, new PluginInstallationListener() {

                                            public void completed() {
                                                ci.out.println("Installation complete");

                                                sem.release();
                                            }

                                            public void cancelled() {
                                                ci.out.println("Installation cancelled");

                                                sem.release();
                                            }

                                            public void failed(PluginException e) {
                                                ci.out.println("Installation failed: " + Debug.getNestedExceptionMessage(e));

                                                sem.release();
                                            }
                                        });

                                instance.addListener(new UpdateCheckInstanceListener() {

                                    public void cancelled(UpdateCheckInstance instance) {
                                        ci.out.println("Installation cancelled");
                                    }

                                    public void complete(UpdateCheckInstance instance) {
                                        Update[] updates = instance.getUpdates();

                                        for (final Update update : updates) {

                                            ResourceDownloader[] rds = update.getDownloaders();

                                            for (ResourceDownloader rd : rds) {

                                                rd.addListener(new ResourceDownloaderAdapter() {
                                                    public void reportActivity(ResourceDownloader downloader, String activity) {
                                                        ci.out.println("\t" + activity);
                                                    }

                                                    public void reportPercentComplete(ResourceDownloader downloader, int percentage) {
                                                        ci.out.println("\t" + percentage + "%");

                                                    }
                                                });

                                                // go ahead and actually initiate the install, someone has to do it!

                                                try {
                                                    rd.download();

                                                } catch (Throwable e) {
                                                }
                                            }

                                            if (update.getRestartRequired() != Update.RESTART_REQUIRED_NO) {

                                                restart_required[0] = true;
                                            }
                                        }
                                    }
                                });

                                sem.reserve();

                                if (restart_required[0]) {

                                    ci.out.println("**** Restart required to complete installation ****");
                                }
                            } catch (Throwable e) {

                                ci.out.println("Install failed: " + Debug.getNestedExceptionMessage(e));
                            }
                        }
                    }.start();

                } catch (Throwable e) {

                    ci.out.println("Install failed: " + Debug.getNestedExceptionMessage(e));
                }
            }
            return;
        }

        // Commands from this point require a plugin ID.
        if (args.size() == 1) {
            ci.out.println("No plugin ID given.");
            ci.out.println();
            return;
        }

        String plugin_id = (String) args.get(1);
        PluginInterface plugin = plugin_manager.getPluginInterfaceByID(plugin_id, false);
        if (plugin == null) {
            ci.out.println("Invalid plugin ID: " + plugin_id);
            ci.out.println();
            return;
        }

        if (subcmd.equals("status")) {
            ci.out.println("ID     : " + plugin.getPluginID());
            ci.out.println("Name   : " + plugin.getPluginName());
            ci.out.println("Version: " + plugin.getPluginVersion());
            ci.out.println("Running: " + plugin.getPluginState().isOperational());
            ci.out.println("Runs at startup: " + plugin.getPluginState().isLoadedAtStartup());
            if (!plugin.getPluginState().isBuiltIn()) {
                ci.out.println("Location: " + plugin.getPluginDirectoryName());
            }
            ci.out.println();
            return;
        }

        if (subcmd.equals("startup")) {
            if (args.size() == 2) {
                ci.out.println("Need to pass either \"on\" or \"off\"");
                ci.out.println();
                return;
            }
            String enabled_mode = (String) args.get(2);
            if (enabled_mode.equals("on")) {
                plugin.getPluginState().setLoadedAtStartup(true);
            } else if (enabled_mode.equals("off")) {
                plugin.getPluginState().setLoadedAtStartup(false);
            } else {
                ci.out.println("Need to pass either \"on\" or \"off\"");
                ci.out.println();
                return;
            }
            ci.out.println("Done.");
            ci.out.println();
            return;
        }

        if (subcmd.equals("uninstall")) {

            PluginInterface pi = plugin_manager.getPluginInterfaceByID(plugin_id, false);

            if (pi == null) {

                ci.out.println("Plugin '" + plugin_id + "' is not installed");

                return;
            }

            final PluginInstaller installer = plugin_manager.getPluginInstaller();

            try {
                final StandardPlugin sp = installer.getStandardPlugin(plugin_id);

                if (sp == null) {

                    ci.out.println("Plugin '" + plugin_id + "' is not a standard plugin");

                    return;
                }

                final PluginInstaller uninstaller = plugin_manager.getPluginInstaller();

                Map<Integer, Object> properties = new HashMap<Integer, Object>();

                final AESemaphore sem = new AESemaphore("plugin-uninstall");

                UpdateCheckInstance instance = uninstaller.uninstall(new PluginInterface[] { pi }, new PluginInstallationListener() {

                    public void completed() {
                        ci.out.println("Uninstallation complete");

                        sem.release();
                    }

                    public void cancelled() {
                        ci.out.println("Uninstallation cancelled");

                        sem.release();
                    }

                    public void failed(PluginException e) {
                        ci.out.println("Uninstallation failed: " + Debug.getNestedExceptionMessage(e));

                        sem.release();
                    }
                }, properties);

                instance.addListener(new UpdateCheckInstanceListener() {

                    public void cancelled(UpdateCheckInstance instance) {
                        ci.out.println("InsUninstallationtallation cancelled");
                    }

                    public void complete(UpdateCheckInstance instance) {
                        Update[] updates = instance.getUpdates();

                        for (final Update update : updates) {

                            ResourceDownloader[] rds = update.getDownloaders();

                            for (ResourceDownloader rd : rds) {

                                try {
                                    rd.download();

                                } catch (Throwable e) {
                                }
                            }
                        }
                    }
                });

                sem.reserve();

                Object obj = properties.get(UpdateCheckInstance.PT_UNINSTALL_RESTART_REQUIRED);

                if (obj instanceof Boolean && (Boolean) obj) {

                    ci.out.println("**** Restart required to complete uninstallation ****");
                }
            } catch (Throwable e) {

                ci.out.println("Uninstall failed: " + Debug.getNestedExceptionMessage(e));

            }
        }
    }
}
